/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.zkex.list.tree;

import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.GetValueRequestMessage;
import cn.easyplatform.messages.request.ListPagingRequestMessage;
import cn.easyplatform.messages.request.SimpleRequestMessage;
import cn.easyplatform.messages.vos.GetValueVo;
import cn.easyplatform.messages.vos.datalist.ListHeaderVo;
import cn.easyplatform.messages.vos.datalist.ListLoadVo;
import cn.easyplatform.messages.vos.datalist.ListPagingVo;
import cn.easyplatform.spi.service.ListService;
import cn.easyplatform.spi.service.TaskService;
import cn.easyplatform.type.*;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.dialog.MessageBox;
import cn.easyplatform.web.ext.zul.Datalist;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.OperableHandler;
import cn.easyplatform.web.task.support.ExpressionEngine;
import cn.easyplatform.web.task.support.SupportFactory;
import cn.easyplatform.web.task.zkex.list.AbstractListBuilder;
import cn.easyplatform.web.task.zkex.list.OperableListSupport;
import cn.easyplatform.web.task.zkex.list.exporter.AbstractExporter;
import cn.easyplatform.web.task.zkex.list.exporter.ExportBean;
import cn.easyplatform.web.task.zkex.list.group.Group;
import cn.easyplatform.web.task.zkex.list.group.LevelGroup;
import cn.easyplatform.web.task.zkex.list.group.ResultHelper;
import cn.easyplatform.web.task.zkex.list.group.TreeGroup;
import cn.easyplatform.web.task.zkex.list.menu.ColumnMenu;
import cn.easyplatform.web.utils.ExtUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.IdSpace;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.*;
import org.zkoss.zul.impl.MeshElement;

import java.util.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public abstract class AbstractTreeBuilder extends AbstractListBuilder implements
        EventListener<Event> {

    protected Tree listExt;

    protected Group group;

    protected ExpressionEngine expressionEngine;

    private ExpressionEngine footerEngine;

    private boolean draggable;

    protected Paging paging;

    /**
     * @param mainTaskHandler
     * @param entity
     */
    public AbstractTreeBuilder(OperableHandler mainTaskHandler, Datalist entity, Component anchor) {
        super(mainTaskHandler, entity, anchor);
        listExt = new Tree();
        listExt.setSpan(entity.isSpan());
        listExt.setSizedByContent(entity.isSizedByContent());
        listExt.setSclass(entity.getSclass());
        listExt.setCheckmark(entity.isCheckmark());
        listExt.setMultiple(entity.isMultiple());
        listExt.setClass(entity.getSclass());
        listExt.setSclass(entity.getSclass());
        listExt.setEvent(entity.getEvent());
        listExt.setId(entity.getId());
        listExt.setVisible(entity.isVisible());
        listExt.setRows(entity.getRows());
        listExt.setStyle(entity.getStyle());
        listExt.setTooltiptext(entity.getTooltiptext());
        listExt.setTooltip(entity.getTooltip());
        listExt.setTop(entity.getTop());
        listExt.setLeft(entity.getLeft());
        listExt.setMold(entity.getMold());
        listExt.setDroppable(entity.getDroppable());
        listExt.setNonselectableTags(entity.getNonselectableTags());
        //if (Contexts.getEnv().getDeviceType().equals(DeviceType.MOBILE.getName()))
        //    listExt.setWidgetAttribute("data-swipeable", "true");
        if (this instanceof AbstractSelectableTreeBuilder) {
            listExt.setHflex("1");
            listExt.setVflex("1");
        } else {
            if (Strings.isBlank(entity.getHeight()))
                listExt.setVflex(entity.getVflex());
            else
                listExt.setHeight(entity.getHeight());
            if (Strings.isBlank(entity.getWidth()))
                listExt.setHflex(entity.getHflex());
            else
                listExt.setWidth(entity.getWidth());
        }
        draggable = !"false".equalsIgnoreCase(entity.getDraggable());
        if (entity.isCheckall()) {
            if (!entity.isCheckmark() || !entity.isMultiple())
                entity.setCheckall(false);
        }
    }

    protected void createHeader() {
        if (!Strings.isBlank(layout.getOnRow()))
            expressionEngine = SupportFactory.getExpressionEngine(this,
                    layout.getOnRow());
        ExpressionEngine headerEngine = null;
        Map<String, Component> managedComponents = null;
        if (!Strings.isBlank(layout.getOnHeader())) {
            headerEngine = SupportFactory.getExpressionEngine(this,
                    layout.getOnHeader());
            managedComponents = new HashMap<String, Component>();
        }
        Treecols head = null;
        if (listExt.getTreecols() == null) {
            head = new Treecols();
            head.setStyle(headStyle);
            head.setParent(listExt);
        } else {
            head = listExt.getTreecols();
            head.getChildren().clear();
        }
        group = null;
        if (!Strings.isBlank(getEntity().getGroup()))
            group = TreeGroup.cast(this, layout.getGroups().get(0), layout);
        else
            group = new LevelGroup(this, layout, getEntity().getLevelBy()
                    .split(","));
        head.setSizable(getEntity().isSizable());
        List<String> invisibleColumns = null;
        String cols = getEntity().getInvisibleColumns();
        if (!Strings.isBlank(cols)) {
            if (cols.startsWith("$")) {
                IResponseMessage<?> resp = ServiceLocator.lookup(
                        TaskService.class).getValue(
                        new GetValueRequestMessage(getId(), new GetValueVo(cols.substring(1))));
                if (!resp.isSuccess())
                    throw new BackendException(resp);
                cols = (String) resp.getBody();
            }
            invisibleColumns = Arrays.asList(cols.split("\\,"));
        }
        int size = layout.getHeaders().size();
        for (int i = 0; i < size; i++) {
            ListHeaderVo hv = layout.getHeaders().get(i);
            Treecol header = new Treecol();
            header.setAttribute("value", hv);
            boolean isVisible = invisibleColumns == null
                    || !invisibleColumns.contains(hv.getName());
            boolean isEditable = false;
            if (!Strings.isBlank(hv.getName()))
                isEditable = (this instanceof OperableListSupport) ? ((OperableListSupport) this)
                        .isEditable(hv.getName()) : false;
            hv.setEditable(isEditable);
            if (isVisible && hv.isVisible()) {
                if (getEntity().isShowTitle()) {
                    if (hv.getTitle() != null && hv.getTitle().startsWith("$")) {
                        useHeaderVariable = true;
                        IResponseMessage<?> resp = ServiceLocator
                                .lookup(TaskService.class).getValue(
                                        new GetValueRequestMessage(getId(),
                                                new GetValueVo(hv.getTitle()
                                                        .substring(1))));
                        if (!resp.isSuccess()) {
                            Clients.wrongValue(listExt, (String) resp.getBody());
                            return;
                        }
                        header.setLabel((String) resp.getBody());
                    } else
                        header.setLabel(hv.getTitle());
                }
                if (!listExt.isSizedByContent()) {
                    if (NumberUtils.isDigits(hv.getWidth()))
                        header.setWidth(hv.getWidth() + "px");
                    else
                        header.setWidth(hv.getWidth());
                }
                if (hv.isTotal())
                    group.addTotal(hv.getTotalType(), i);
                if (!Strings.isBlank(hv.getAlign()))
                    header.setAlign(hv.getAlign());
                else if (hv.getType() == FieldType.INT
                        || hv.getType() == FieldType.LONG
                        || hv.getType() == FieldType.NUMERIC)
                    header.setAlign("right");
                if (!Strings.isBlank(hv.getValign()))
                    header.setValign(hv.getValign());
                if (!Strings.isBlank(hv.getStyle()))
                    header.setStyle(hv.getStyle());
                if (!Strings.isBlank(hv.getIconSclass()))
                    header.setIconSclass(hv.getIconSclass());
                if (!Strings.isBlank(hv.getHoverimg()))
                    header.setHoverImage(hv.getHoverimg());
                if (!Strings.isBlank(hv.getImage()))
                    header.setImage(hv.getImage());
                if (!Strings.isBlank(hv.getStyle()))
                    header.setStyle(hv.getStyle());
                if (!hasTotal)
                    hasTotal = hv.isTotal();
                if (hv.isSort()) {
                    if (Strings.isBlank(getEntity().getLevelBy())) {
                        if (getEntity().getType().equals(Constants.DETAIL)
                                || Strings.isBlank(hv.getField())) {
                            Comparator<Treeitem> comparator1 = ExtUtils
                                    .createSortComparator(true, i);
                            Comparator<Treeitem> comparator2 = ExtUtils
                                    .createSortComparator(false, i);
                            header.setSortAscending(comparator1);
                            header.setSortDescending(comparator2);
                        } else {
                            Comparator<Treeitem> comparator = ExtUtils
                                    .createEmptyComparator();
                            header.setSortAscending(comparator);
                            header.setSortDescending(comparator);
                            header.addEventListener(Events.ON_SORT, this);
                        }
                    }
                    header.setTooltiptext(Labels.getLabel("datalist.header.order"));
                } else
                    header.setTooltiptext(hv.getName());
            } else {
                hv.setVisible(false);
                header.setVisible(false);
                //header.setStubonly(true);
                header.setLabel(hv.getTitle());
            }
            if (managedComponents != null)
                managedComponents.put(hv.getName(), header);
            head.appendChild(header);
        }
        head.setVisible(getEntity().isShowTitle());
        if ((getEntity().getPopup() == null || getEntity().getPopup()
                .equalsIgnoreCase("true"))
                && head.getAttribute("menupopup") == null)
            setMenupopup(head);
        if (getEntity().getFrozenColumns() > 0) {
            Frozen frozen = null;
            if (listExt.getFrozen() == null) {
                frozen = new Frozen();
                listExt.appendChild(frozen);
            } else
                frozen = listExt.getFrozen();
            frozen.setColumns(getEntity().getFrozenColumns());
            frozen.setStyle(getEntity().getFrozenStyle());
            frozen.setRightColumns(getEntity().getFrozenRightColumns());
            frozen.setStart(getEntity().getFrozenStart());
        }
        invisibleColumns = null;
        if (managedComponents != null) {
            managedComponents.put("self", head);
            headerEngine.exec(null, managedComponents, null);
            headerEngine = null;
            managedComponents = null;
        }
        group.getRoot().setParent(listExt);
        // foot
        if (hasTotal) {
            if (!Strings.isBlank(layout.getOnFooter()))
                footerEngine = SupportFactory.getExpressionEngine(this,
                        layout.getOnFooter());
            Treefoot foot = null;
            if (listExt.getTreefoot() == null) {
                foot = new Treefoot();
                foot.setStyle(footStyle);
                listExt.appendChild(foot);
            } else {
                foot = listExt.getTreefoot();
                foot.getChildren().clear();
            }
            for (ListHeaderVo hv : layout.getHeaders()) {
                Treefooter footer = new Treefooter();
                if (hv.isTotal()) {
                    footer.setStyle(hv.getTotalStyle());
                    if (!Strings.isBlank(hv.getAlign()))
                        footer.setAlign(hv.getAlign());
                    else if (hv.getType() == FieldType.INT
                            || hv.getType() == FieldType.LONG
                            || hv.getType() == FieldType.NUMERIC)
                        footer.setAlign("right");
                }
                foot.appendChild(footer);
            }
        }
    }

    @Override
    protected void redraw(List<ListRowVo> rowset) {
        if (rowset == null || rowset.isEmpty())
            return;
        try {
            if (expressionEngine != null)
                expressionEngine.compile();
            Map<String, Object> evalMap = new HashMap<String, Object>();
            Map<String, Component> managedComponents = new HashMap<String, Component>();
            ResultHelper rh = new ResultHelper(rowset);
            Treeitem lastSelectedItem = null;
            while (rh.hasNext()) {
                ListRowVo rv = rh.next();
                evalMap.clear();
                managedComponents.clear();
                evalMap.put("LIST_INDEX", ++listIndex);
                Treeitem li = group.createGroup(rh, evalMap, managedComponents,
                        0);
                li.setValue(rv);
                li.setSelected(rv.isSelected());
                if (!li.isSelected())
                    validate(li);
                if (li.isSelected())
                    lastSelectedItem = li;
                if (expressionEngine != null)
                    expressionEngine.eval(li, evalMap, managedComponents);
                if (clickType > 0) {
                    li.setEvent(listExt.getEvent());
                    addEventListener(li, clickType == 1 ? Events.ON_CLICK
                            : Events.ON_DOUBLE_CLICK);
                }
                if (!StringUtils.isBlank(getEntity().getLazyQuery())) {
                    li.appendChild(new Treechildren());
                    if (getEntity().getDepth() > li.getLevel()) {
                        load(li);
                        li.setOpen(true);
                    } else {
                        li.addEventListener(Events.ON_OPEN, this);
                        li.setOpen(false);
                    }
                }
            }
            if (StringUtils.isBlank(getEntity().getLazyQuery())) {
                if (getEntity().getDepth() == -1 || getEntity().getDepth() > 0) {
                    for (Treeitem ti : listExt.getItems()) {
                        if (ti.isContainer()) {
                            if (getEntity().getDepth() == -1
                                    || getEntity().getDepth() <= ti.getLevel())
                                ti.setOpen(false);
                        }
                    }
                } else if (lastSelectedItem != null)
                    Clients.scrollIntoView(lastSelectedItem);
            }
            evalMap = null;
            managedComponents = null;
            //refreshFoot();
        } finally {
            if (expressionEngine != null)
                expressionEngine.destroy();
        }
    }

    @Override
    public void refreshFoot() {
        if (!hasTotal)
            return;
        Treefoot foot = listExt.getTreefoot();
        Map<String, Component> managedComponents = null;
        Map<String, Object> evalMap = null;
        if (footerEngine != null) {
            managedComponents = new HashMap<String, Component>();
            evalMap = new HashMap<String, Object>();
            managedComponents.put("self", foot);
        }
        for (int i = 0; i < layout.getHeaders().size(); i++) {
            ListHeaderVo hv = layout.getHeaders().get(i);
            if (hv.isTotal()) {
                Treefooter footer = (Treefooter) foot.getChildren().get(i);
                if (Strings.isBlank(hv.getTotalName()))
                    footer.setLabel(hv.getTitle() + ":" + hv.getValue());
                else
                    footer.setLabel(hv.getTotalName() + ":" + hv.getValue());
                if (footerEngine != null) {
                    evalMap.put("SYS_COUNT", hv.getCount());
                    evalMap.put("SYS_TOTAL", hv.getTotal());
                }
            }
            if (footerEngine != null) {
                managedComponents.put(hv.getName(), foot.getChildren().get(i));
                evalMap.put(hv.getName(), hv.getRawValue());
            }
        }
        if (footerEngine != null)
            footerEngine.exec(null, managedComponents, evalMap);
    }

    protected Treeitem createRow(ListRowVo rv) {
        try {
            if (expressionEngine != null)
                expressionEngine.compile();
            Map<String, Object> evalMap = new HashMap<String, Object>();
            Map<String, Component> managedComponents = new HashMap<String, Component>();
            Treeitem li = new Treeitem();
            li.setValue(rv);
            if (isEditable())
                li.setSelected(true);
            li.setParent(group.getRoot());
            createRow(li, rv, evalMap, managedComponents);
            return li;
        } finally {
            if (expressionEngine != null)
                expressionEngine.destroy();
        }
    }

    protected void createRow(Treeitem li, ListRowVo rv,
                             Map<String, Object> evalMap,
                             Map<String, Component> managedComponents) {
        if (!Strings.isBlank(rowStyle))
            li.setStyle(rowStyle);
        if (group instanceof TreeGroup)
            ((TreeGroup) group).createRow(li, rv, evalMap, managedComponents);
        else
            ((LevelGroup) group).createRow(li, rv, evalMap, managedComponents);
        if (expressionEngine != null)
            expressionEngine.eval(li, evalMap, managedComponents);
        if (clickType > 0) {
            li.setEvent(listExt.getEvent());
            addEventListener(li, clickType == 1 ? Events.ON_CLICK
                    : Events.ON_DOUBLE_CLICK);
        }
        if (draggable)
            li.setDraggable(getEntity().getDraggable());
    }

    @Override
    public void onEvent(Event event) throws Exception {
        if (event.getName().equals(Events.ON_SORT)) {
            doSort((Treecol) event.getTarget(), (Boolean) event.getData());
        } else if (event.getTarget() instanceof Menuitem) {
            String id = (String) event.getTarget().getAttribute("id");
            id = id == null ? "" : id;
            if (id.equals("--exp--")) {
                AbstractExporter.createExporter(mainTaskHandler, this)
                        .doExport();
            } else if (id.equals("--filter--")) {
                ColumnMenu menu = (ColumnMenu) event.getTarget().getParent();
                filter(menu.getHeader());
            }
        } else if (event.getName().equals(Events.ON_OPEN)) {//延时加载
            OpenEvent oe = (OpenEvent) event;
            if (oe.isOpen()) {
                Treeitem ti = (Treeitem) oe.getTarget();
                if (ti.getTreechildren() != null && ti.getTreechildren().getFirstChild() == null)
                    load(ti);
            }
        } else if (event.getName().equals("onLater")) {//导出提示
            Session session = event.getTarget().getDesktop().getSession();
            int maxInactiveInterval = session.getMaxInactiveInterval();
            //如果数据量大，防止会话过期
            session.setMaxInactiveInterval(-1);
            ExportBean eb = (ExportBean) event.getData();
            Tree backup = listExt;
            try {
                listExt = (Tree) listExt.clone();
                if (listExt.getChildren() != null)
                    listExt.getTreechildren().detach();
                listExt.detach();
                clear();
                group.getRoot().setParent(listExt);
                //每500笔一次导出，减少服务端内存
                final int PZ = 500;
                int count = paging.getTotalSize() / PZ;
                if (paging.getTotalSize() % PZ > 0) {
                    count++;
                }
                for (int i = 1; i <= count; i++) {
                    ListService ds = ServiceLocator.lookup(ListService.class);
                    IResponseMessage resp = ds.doPaging(new ListPagingRequestMessage(getId(), new ListPagingVo(getEntity().getId(), i, PZ)));
                    if (resp.isSuccess()) {
                        redraw((List<ListRowVo>) resp.getBody());
                    } else {
                        MessageBox.showMessage(resp);
                        return;
                    }
                }
                export(listExt, eb.getType(), eb.getExportHeaders());
            } finally {
                listExt = null;
                listExt = backup;
                group.getRoot().setParent(listExt);
                session.setMaxInactiveInterval(maxInactiveInterval);
                listExt.removeEventListener("onLater", this);
                Clients.clearBusy();
            }
        }
    }

    private void load(Treeitem ti) {
        ListRowVo vo = ti.getValue();
        String field = StringUtils.substringBefore(getEntity().getLevelBy(), ",");
        FieldVo param = null;
        int idx = 0;
        for (; idx < vo.getData().length; idx++) {
            ListHeaderVo hv = null;
            if (getEntity().isShowRowNumbers())
                hv = layout.getHeaders().get(idx + 1);
            else hv = layout.getHeaders().get(idx);
            if (hv.getName().equalsIgnoreCase(field)) {
                param = new FieldVo(hv.getType(), vo.getData()[idx]);
                break;
            }
        }
        if (param == null) {
            ti.getFirstChild().detach();
            return;
        }
        ListService ds = ServiceLocator.lookup(ListService.class);
        IResponseMessage<?> resp = ds.doLoad(new SimpleRequestMessage(getId(), new ListLoadVo(getEntity().getId(), getEntity().getLazyQuery(), param)));
        if (resp.isSuccess()) {
            List<ListRowVo> data = (List<ListRowVo>) resp.getBody();
            if (data.isEmpty())
                ti.getTreechildren().detach();
            else {
                for (ListRowVo rv : data) {
                    Treeitem node = createRow(rv);
                    ti.getTreechildren().appendChild(node);
                    param.setValue(rv.getData()[idx]);
                    resp = ds.doLoad(new SimpleRequestMessage(getId(), new ListLoadVo(getEntity().getId(), getEntity().getLazyQuery(), param, true)));
                    if (resp.isSuccess()) {
                        Integer count = (Integer) resp.getBody();
                        if (count > 0) {
                            node.appendChild(new Treechildren());
                            if (getEntity().getDepth() > node.getLevel()) {
                                load(node);
                                node.setOpen(true);
                            } else {
                                node.addEventListener(Events.ON_OPEN, this);
                                node.setOpen(false);
                            }
                        }
                    }
                }
            }
        } else {
            ti.getTreechildren().detach();
            Clients.wrongValue(ti, (String) resp.getBody());
        }
    }

    protected void setEmptyMessage(String msg) {
        MessageBox.showMessage("datalist." + getEntity().getId(), msg);
    }

    public void clear() {
        if (listExt.getTreechildren() != null) {
            try {
                listExt.getTreechildren().getChildren().clear();
            } catch (Exception e) {
            }
        }
        group.clear();
        for (ListHeaderVo header : layout.getHeaders()) {
            if (header.isTotal())
                header.reset();
        }
        refreshFoot();
        listIndex = 0;
    }

    @Override
    public ListRowVo getSelectedValue() {
        Treeitem selectedItem = null;
        Event evt = Contexts.getEvent();
        if (evt == null || evt.getTarget().getParent() == null) {
            selectedItem = listExt.getSelectedItem();
            if (selectedItem != null)
                return selectedItem.getValue();
            else
                return null;
        }
        Component c = evt.getTarget();
        if (c instanceof Tree) {
            if (c == listExt) {
                selectedItem = listExt.getSelectedItem();
                if (selectedItem != null)
                    return selectedItem.getValue();
            }
            return null;
        }
        if (c instanceof Treeitem) {
            selectedItem = (Treeitem) c;
            if (selectedItem.getTree() == listExt)
                return selectedItem.getValue();
            else
                return null;
        }
        Component parent = c.getParent();
        int deep = 0;
        while (deep < 6) {
            if (parent instanceof Treeitem) {
                selectedItem = (Treeitem) parent;
                if (selectedItem.getTree() == listExt) {
                    selectedItem.setSelected(true);
                    return selectedItem.getValue();
                } else
                    return null;
            } else {
                parent = parent.getParent();
                if (parent == null || parent instanceof Treechildren
                        || parent instanceof IdSpace)
                    break;
            }
            deep++;
        }
        selectedItem = listExt.getSelectedItem();
        if (selectedItem != null)
            return selectedItem.getValue();
        return null;
    }

    @Override
    public void exportAll(String type, Object... exportHeaders) {
        if (getEntity().getType().equals(Constants.DETAIL) || getEntity().isFetchAll() || getEntity().getPageSize() == 0) {
            export(listExt, type, exportHeaders);
        } else {
            Clients.showBusy(Labels.getLabel("message.long.operation"));
            listExt.addEventListener("onLater", this);
            Events.echoEvent("onLater", listExt, new ExportBean(type, exportHeaders));
        }
    }

    @Override
    public MeshElement getComponent() {
        return listExt;
    }

    /**
     * 记录是否被选中，多选时使用
     *
     * @param c
     * @return
     */
    public boolean validate(Component c) {
        return false;
    }
}
